{
 "cells": [
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T09:15:15.347925Z",
     "start_time": "2025-02-26T09:15:12.092182Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "    \n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 2.0.2\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.1\n",
      "torch 2.6.0+cu126\n",
      "cuda:0\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 准备数据"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T09:15:19.241821Z",
     "start_time": "2025-02-26T09:15:18.975962Z"
    }
   },
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing(data_home='data')\n",
    "print(housing.DESCR)\n",
    "print(housing.data.shape)\n",
    "print(housing.target.shape)"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".. _california_housing_dataset:\n",
      "\n",
      "California Housing dataset\n",
      "--------------------------\n",
      "\n",
      "**Data Set Characteristics:**\n",
      "\n",
      ":Number of Instances: 20640\n",
      "\n",
      ":Number of Attributes: 8 numeric, predictive attributes and the target\n",
      "\n",
      ":Attribute Information:\n",
      "    - MedInc        median income in block group\n",
      "    - HouseAge      median house age in block group\n",
      "    - AveRooms      average number of rooms per household\n",
      "    - AveBedrms     average number of bedrooms per household\n",
      "    - Population    block group population\n",
      "    - AveOccup      average number of household members\n",
      "    - Latitude      block group latitude\n",
      "    - Longitude     block group longitude\n",
      "\n",
      ":Missing Attribute Values: None\n",
      "\n",
      "This dataset was obtained from the StatLib repository.\n",
      "https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html\n",
      "\n",
      "The target variable is the median house value for California districts,\n",
      "expressed in hundreds of thousands of dollars ($100,000).\n",
      "\n",
      "This dataset was derived from the 1990 U.S. census, using one row per census\n",
      "block group. A block group is the smallest geographical unit for which the U.S.\n",
      "Census Bureau publishes sample data (a block group typically has a population\n",
      "of 600 to 3,000 people).\n",
      "\n",
      "A household is a group of people residing within a home. Since the average\n",
      "number of rooms and bedrooms in this dataset are provided per household, these\n",
      "columns may take surprisingly large values for block groups with few households\n",
      "and many empty houses, such as vacation resorts.\n",
      "\n",
      "It can be downloaded/loaded using the\n",
      ":func:`sklearn.datasets.fetch_california_housing` function.\n",
      "\n",
      ".. rubric:: References\n",
      "\n",
      "- Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,\n",
      "  Statistics and Probability Letters, 33 (1997) 291-297\n",
      "\n",
      "(20640, 8)\n",
      "(20640,)\n"
     ]
    }
   ],
   "execution_count": 2
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T09:15:26.751384Z",
     "start_time": "2025-02-26T09:15:26.747797Z"
    }
   },
   "source": [
    "# print(housing.data[0:5])\n",
    "import pprint  #打印的格式比较 好看\n",
    "\n",
    "pprint.pprint(housing.data[0:5])\n",
    "print('-'*50)\n",
    "pprint.pprint(housing.target[0:5])"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "array([[ 8.32520000e+00,  4.10000000e+01,  6.98412698e+00,\n",
      "         1.02380952e+00,  3.22000000e+02,  2.55555556e+00,\n",
      "         3.78800000e+01, -1.22230000e+02],\n",
      "       [ 8.30140000e+00,  2.10000000e+01,  6.23813708e+00,\n",
      "         9.71880492e-01,  2.40100000e+03,  2.10984183e+00,\n",
      "         3.78600000e+01, -1.22220000e+02],\n",
      "       [ 7.25740000e+00,  5.20000000e+01,  8.28813559e+00,\n",
      "         1.07344633e+00,  4.96000000e+02,  2.80225989e+00,\n",
      "         3.78500000e+01, -1.22240000e+02],\n",
      "       [ 5.64310000e+00,  5.20000000e+01,  5.81735160e+00,\n",
      "         1.07305936e+00,  5.58000000e+02,  2.54794521e+00,\n",
      "         3.78500000e+01, -1.22250000e+02],\n",
      "       [ 3.84620000e+00,  5.20000000e+01,  6.28185328e+00,\n",
      "         1.08108108e+00,  5.65000000e+02,  2.18146718e+00,\n",
      "         3.78500000e+01, -1.22250000e+02]])\n",
      "--------------------------------------------------\n",
      "array([4.526, 3.585, 3.521, 3.413, 3.422])\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T09:15:32.437598Z",
     "start_time": "2025-02-26T09:15:32.390706Z"
    }
   },
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "#拆分训练集和测试集，random_state是随机种子,同样的随机数种子，是为了得到同样的随机值\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state = 7)\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state = 11)\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "dataset_maps = {\n",
    "    \"train\": [x_train, y_train],\n",
    "    \"valid\": [x_valid, y_valid],\n",
    "    \"test\": [x_test, y_test],\n",
    "}\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "execution_count": 4
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T09:15:35.348544Z",
     "start_time": "2025-02-26T09:15:35.339008Z"
    }
   },
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "scaler = StandardScaler()\n",
    "scaler.fit(x_train)"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StandardScaler()"
      ],
      "text/html": [
       "<style>#sk-container-id-1 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: #000;\n",
       "  --sklearn-color-text-muted: #666;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-1 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-1 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: flex;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "  align-items: start;\n",
       "  justify-content: space-between;\n",
       "  gap: 0.5em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label .caption {\n",
       "  font-size: 0.6rem;\n",
       "  font-weight: lighter;\n",
       "  color: var(--sklearn-color-text-muted);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-1 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-1 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-1 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 0.5em;\n",
       "  text-align: center;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-1 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-1\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-1\" type=\"checkbox\" checked><label for=\"sk-estimator-id-1\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>StandardScaler</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.6/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 5
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 构建数据集"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T09:15:45.567959Z",
     "start_time": "2025-02-26T09:15:45.549675Z"
    }
   },
   "source": [
    "from torch.utils.data import Dataset\n",
    "\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, mode='train'):\n",
    "        self.x, self.y = dataset_maps[mode]\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float()\n",
    "        self.y = torch.from_numpy(self.y).float().reshape(-1, 1)\n",
    "            \n",
    "    def __len__(self):\n",
    "        return len(self.x)\n",
    "    \n",
    "    def __getitem__(self, idx):\n",
    "        return self.x[idx], self.y[idx]\n",
    "    \n",
    "    \n",
    "train_ds = HousingDataset(\"train\")\n",
    "valid_ds = HousingDataset(\"valid\")\n",
    "test_ds = HousingDataset(\"test\")"
   ],
   "outputs": [],
   "execution_count": 6
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T09:15:46.294840Z",
     "start_time": "2025-02-26T09:15:46.287045Z"
    }
   },
   "source": [
    "train_ds[1]"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([-0.2981,  0.3523, -0.1092, -0.2506, -0.0341, -0.0060,  1.0806, -1.0611]),\n",
       " tensor([1.5140]))"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 7
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### DataLoader"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T09:15:55.311284Z",
     "start_time": "2025-02-26T09:15:55.308023Z"
    }
   },
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "\n",
    "batch_size = 8  #过大会导致GPU内存溢出，过小会导致训练时间过长\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "outputs": [],
   "execution_count": 8
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 定义模型"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T09:30:41.688150Z",
     "start_time": "2025-02-26T09:30:41.682634Z"
    }
   },
   "source": [
    "#回归模型我们只需要1个数\n",
    "\n",
    "class WideDeep(nn.Module):\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()\n",
    "        self.deep = nn.Sequential(\n",
    "            nn.Linear(input_dim, 30), #30个神经元\n",
    "            nn.ReLU(),\n",
    "            nn.Linear(30, 30), #30个神经元\n",
    "            nn.ReLU()\n",
    "            )\n",
    "        # pytorch 需要自行计算输出输出维度\n",
    "        self.output_layer = nn.Linear(30 + input_dim, 1)\n",
    "        \n",
    "        # 初始化权重\n",
    "        self.init_weights()\n",
    "        \n",
    "    def init_weights(self):\n",
    "        \"\"\"使用 xavier 均匀分布来初始化全连接层的权重 W\"\"\"\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, nn.Linear):\n",
    "                nn.init.xavier_uniform_(m.weight)\n",
    "                nn.init.zeros_(m.bias)\n",
    "        \n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 8]\n",
    "        deep_output = self.deep(x)\n",
    "        # print(deep_output.shape)\n",
    "        # concat [batch size, 30] with x [batch size 8]，得到 [batch size, 38]\n",
    "        concat = torch.cat([x, deep_output], dim=1)\n",
    "        logits = self.output_layer(concat) # 输出层，输入维度是 38，输出维度是 1\n",
    "        # logits.shape [batch size, 1]\n",
    "        return logits"
   ],
   "outputs": [],
   "execution_count": 16
  },
  {
   "cell_type": "code",
   "source": [
    "# train_ds[0][0]\n",
    "#验证模型是否正确\n",
    "input=train_ds[0][0].reshape(1, -1)\n",
    "print(input.shape)\n",
    "model=WideDeep()\n",
    "out=model(input)\n",
    "out.shape"
   ],
   "metadata": {
    "collapsed": false,
    "ExecuteTime": {
     "end_time": "2025-02-26T09:17:54.780966Z",
     "start_time": "2025-02-26T09:17:54.761081Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "torch.Size([1, 8])\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "torch.Size([1, 1])"
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 10
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T09:17:55.088475Z",
     "start_time": "2025-02-26T09:17:55.084604Z"
    }
   },
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "        \n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else: \n",
    "            self.counter += 1\n",
    "            \n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ],
   "outputs": [],
   "execution_count": 11
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T09:17:55.329972Z",
     "start_time": "2025-02-26T09:17:55.325325Z"
    }
   },
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)         # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "        \n",
    "    return np.mean(loss_list)\n"
   ],
   "outputs": [],
   "execution_count": 12
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 训练"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T09:18:29.486523Z",
     "start_time": "2025-02-26T09:17:56.234320Z"
    }
   },
   "source": [
    "# 训练\n",
    "def training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=None,\n",
    "    early_stop_callback=None,\n",
    "    eval_step=500,\n",
    "    ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "    \n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    " \n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "                \n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "                \n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "                    \n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "        \n",
    "    return record_dict\n",
    "        \n",
    "\n",
    "epoch = 10\n",
    "\n",
    "model = WideDeep()\n",
    "\n",
    "# 1. 定义损失函数 采用交叉熵损失\n",
    "loss_fct = nn.MSELoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.0)\n",
    "\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model, \n",
    "    train_loader, \n",
    "    val_loader, \n",
    "    epoch, \n",
    "    loss_fct, \n",
    "    optimizer, \n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    "    )"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/14520 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "305a35a5bba24b56bb67eeb59e6f2409"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n",
      "IOPub message rate exceeded.\n",
      "The Jupyter server will temporarily stop sending output\n",
      "to the client in order to avoid crashing it.\n",
      "To change this limit, set the config variable\n",
      "`--ServerApp.iopub_msg_rate_limit`.\n",
      "\n",
      "Current values:\n",
      "ServerApp.iopub_msg_rate_limit=1000.0 (msgs/sec)\n",
      "ServerApp.rate_limit_window=3.0 (secs)\n",
      "\n"
     ]
    }
   ],
   "execution_count": 13
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T09:18:32.645933Z",
     "start_time": "2025-02-26T09:18:32.547010Z"
    }
   },
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "plot_learning_curves(record, sample_step=500)  #横坐标是 steps"
   ],
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAhYAAAGwCAYAAAD16iy9AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAZn5JREFUeJzt3Qd4VFXaB/D/zKSHFEgINfTeq0gRcWmChWJbQcWyKgprYdf27aqga991bYht7aLYQEUQ6b13kN57J71Mkvme90zuMAlJmEnuTGbu/f+e5z4zyUxm7pnMzH3vOe95j8XhcDhAREREpAOrHg9CREREJBhYEBERkW4YWBAREZFuGFgQERGRbhhYEBERkW4YWBAREZFuGFgQERGRbkLgZwUFBTh69ChiYmJgsVj8/fRERERUDlL2Ki0tDbVr14bVag2cwEKCiuTkZH8/LREREeng0KFDqFu3buAEFtJToe1YbGysbo9rt9vx+++/Y8CAAQgNDYVZmLXdZm472812m4FZ2x3IbU9NTVUdA9pxPGACC234Q4IKvQOLqKgo9ZiB9I/wNbO228xtZ7vZbjMwa7uDoe2XSmNg8iYRERHphoEFERER6YaBBREREenG7zkWRERkPFJKIDc3V9c8g5CQEGRnZyM/Px9mYq+ktks+h81mq/DjMLAgIqIKkYBi3759KrjQs2ZCzZo11QxCs9U8clRi2+Pj49VzV+R5GVgQEVGFDoLHjh1TZ7oyFbGswknekCAlPT0dVapU0e0xg0VBJbRd/o+ZmZk4efKk+rlWrVrlfiwGFkREVG55eXnqgCTVGGWKpN5DKxEREaYMLHIroe2RkZHqUoKLpKSkcg+LmOu/RUREutJyAMLCwip7V0gHWnAoeR7lxcCCiIgqzGx5EEZl0eH/yMCCiIiIdMPAgoiIiHTDwIKIiKgCGjRogDfeeEOXx1qwYIFKmkxJSUGwMs6sEHsmYjMPAAWSSBR4i7YQEVHg6NOnDzp06KBLQLB69WpER0frsl9GYIzAoqAAIa83w1V52bCnDgaqN6nsPSIioiAmdR1kxotUwLyU6tWr+2WfgoUxhkJknm/Vhuqq5fSuyt4bIiLTUoWWcvN02bJy8726vzy3J+68804sXLgQb775ppoFIdunn36qLmfOnInOnTsjPDwcS5YswZ49ezBkyBDUqFFDFazq2rUr5syZU+ZQiMViwUcffYRhw4ap6ZtNmzbFzz//XO7X9IcffkDr1q3VPslz/ec//yly+7vvvqueQ+peyH7eeOONrtu+//57tG3bVtWoSEhIQL9+/ZCRkQFfCjHMmzmhKSyntsFyRgKLQZW9O0REppRlz0erZ2ZVynP/8dxARIVd+rAmAcXOnTvRpk0bPPfcc+p3W7duVZdPPvkk/v3vf6NRo0aoWrWqKqs9ePBgvPDCC+rA/vnnn+O6667Djh07UK9evVKfY8KECXj11Vfx2muv4e2338bIkSNx4MABVKtWzas2rV27FjfffDPGjx+PW265BcuWLcODDz6oggQJkNasWYOHHnoIX3zxBXr06IGzZ89i8eLF6m+lIuqtt96q9kOCnLS0NHWbpwFYeRkosCgc/jizu7J3hYiIAlhcXJwq6CW9CbIuhti+fbu6lECjf//+rvtKINC+fXvXz88//zymTp2qeiDGjh1b6nPceeed6qAuXnzxRbz11ltYtWoVrr76aq/29fXXX0ffvn3x9NNPq5+bNWuGP/74QwUs8hwHDx5U+R3XXnstYmJiUL9+fXTs2NEVWEhl1OHDh6vfC+m98DXDBRbOHgsiIqoMkaE21XOgR1nrtNQ0xMTGeFzWWp67orp06VLkZ1mzQ3oLfv31V9eBOisrSx3Qy9KuXTvXdTnwx8bGutbh8Ma2bdvUUIy7nj17qqEXyQGRIEiCBulhkaBFNm0IRgIiCUokmBg4cCAGDBighkmkJ8aXjJFjIRKaqgsLeyyIiCqN5BfIcIQeW2SYzav761E1svjsjr///e+qh0J6HWQYYcOGDepAfakl4kNDi85OlH3Tc/VXjfRSrFu3Dl9//bVaOOyZZ55RAcX58+fVtNXZs2ervJFWrVqpIZnmzZurlWh9yWq4HouMk0DW+creHSIiCmAyFKKtc1KWpUuXqiEH6QWQgEKGTvbv3w9/admypdqH4vskQyLaImEyc0WSMiWXYtOmTWr/5s2b5wpopIdDcj7Wr1+v2i2Bki8ZZigE4THICq2KSPs5Z55F3aLdWURERBqZXbFy5Up1EJbZHqX1Jshsix9//FElbMpBWnIdfNHzUJq//e1vaiaK5HZI8uby5cvxzjvvqJkgYvr06di7dy969+6thjhmzJih9k96JqR9c+fOVUMgslqp/Hzq1CkVrPiSYXosRHp44frxp3dW9q4QEVEAkyEOOeOXIQKpQ1FazoQkT8oBW2ZcSHAhuQqdOnXy23526tQJ3377Lb755hs1i0WGOiTBVHpRRHx8vAp8/vSnP6mA4b333lPDIjI9VfI6Fi1apGa1SA/HP//5TzVVddAg386cDDFUYBFRC9XT/2BgQUREZZIDrZz9u9MO1sV7NrRhBc2YMWOK/Fx8aMRRwnROyXnwtCKoDNGkpqa6fnfDDTeorSS9evVSZcBLIoHGb7/9Bn8zVI9FWoTWY8GZIURERJXBUIFFenht5xX2WBARUQAaPXq0yukoaZPbjMBgQyHOQic4uw/ItwM2LkZGRESB47nnnlP5HSWRnAjTBRYy1iQlSYuT8qITJ05EZcsKrQZHaBQs9kzg3AEgkYuRERFR4EhKSlKbkVm9XRpWKo9pmxTeEDfddBMCgsUKVGvsvM7hECIiosAOLGRKjhQH0TaZP9u4cWNceeWVCBSORGcFTgYWREREQZRjIeVMv/zyS4wbN67MMqo5OTlq02hTaOx2u9r0oj1WXnwjhEmd+VM7ka/j4wcqrd16vpbBwqxtZ7vZ7kAi+yXTK6Uok56Fo7Qpm9pjm4mjEtsuzyfPK/9XrbKnxtP3oMVRzvVTpWDHiBEjVFGR2rULZ2OUQBZvkVKixU2ePFktkqK32udWoOv+d3EmuimWNHOuBkdERL4h5aSlBzs5OVmVi6bgJp0GslT88ePH1YJr7jIzM9VxPyUlpcxE03IHFlJ9TN5Ev/zyS5n3K6nHQt6Ap0+f1jUDViIpyfkY0L42Ij/tB0dEPPLG7ZJC6TAyrd2ywl3xRW+MzqxtZ7vZ7kCSnZ2tDkSS3B8REaHb48qhKS0tTS2ypcfiYsHEUYltl/+nFPyS43Tx/6ccvxMTEy8ZWJRrKERmhsyZM0eVEb2U8PBwtRUnHxBffEhCkppJvARL9nmE2lOB6ESYga9ez2Bg1raz3eYSqO2WKpFy8JOlzT1d3twT2hCA9tiBRgKpRx55RG2XYrFY1MJfQ4cODfi2y/PJ85b0fvP0/VeuPf7kk0/UdJlrrrkGASc0CohPdl5nAicREZFfeR1YSCQlgcWoUaPU2FpAStBmhrC0NxERUUAHFjIEIgmbd999NwJWogyHsMeCiMjvJG0vN0OfTYodenN/D1MGP/jgAzXpoPiMiyFDhqhj2549e9T1GjVqqFLbsmy5HPv0snnzZrUaaWRkJBISEnDfffchPT3ddbssKta3b1+VYyGrl/bs2dNVnHLjxo246qqr1G2S59C5c2esWbMGgcTrLgdZ172c+Z7+46plwR4LIiK/kmDgxdJnCnpz1hvv7R/931EgLPqSd5Oijn/9618xf/58dQAXZ8+eVSuBzpgxQx3kZanxF154QeUIfv7552rJ9B07dqBevXqoiIyMDDX5oXv37qro5MmTJ/GXv/wFY8eOxaeffqpmYgwfPhy33367Wipdfl61apUriXPkyJHo2LEjJk2apKaDbtiwIeBybwJ0LKOC2GNBRESlqFq1KgYNGqTKHmiBxffff69mPEhvgCQwtm/f3nX/559/XiVf/vzzzyoAqIjJkyermRcSrERHO4Ogd955RwUur7zyigoSZNbF1VdfrQpQyr7I8ucaGTF47LHH0KJFC/Vz06aFJ9IBxKCBReELff4AkJcDhFw8K4WIiHyUQC89BxUkwxSpaWmIjYnxfGaEPLeH5Mz/3nvvxbvvvqt6Jb766iv8+c9/Vs8lPRZSg+nXX39Vy1dIr0FWVpY6qFfUtm3bVNCiBRVChjqkvdIj0rt3b5XDeMMNN6Bfv35qmvHNN9+MWrVqqftKUUrp4fjiiy/U7dL7IgFIIAm8OTx6qFIDCI8FHAXA2b2VvTdEROYhXfYyHKHHJoGCN/f3ouaD9BDIsL4ED1KHY/HixSrYELL6qPRQvPjii+r3MtzQtm1bVTzKHz7++GP8/vvv6NGjB6ZMmYJmzZphxYoV6jYJeLZu3apmZc6bNw+tWrVS+xpIjBlYyJuLa4YQEVEppPiT5DJIT8XXX3+N5s2bo1OnTuq2pUuX4s4778SwYcNUQCGVRaVolB5atmypEjAl10Ijzyc9JbIPmnbt2uHJJ5/EsmXL0KZNGzWEopFA49FHH1XBh7RBZmoGEmMGFoJ5FkREVAbpoZAeC+kh0HortLwFKQApPRUSBEgZa73W7Bg5cqQKamS4Y8uWLSqBVBJJJVlTZqHs27cP//d//6cSNmUmiAQPu3btUgGJDMdIjofMGpHbJCCRBFD3HIxAYMwcC5HQxHl5endl7wkREQUgmfJZrVo1ldsgwYPm9ddfV9NOZShCEjqfeOIJ1wKaFRUVFYVZs2bh4YcfVtNY5WfJp5Dn1G7fvn07PvvsMzVTRXIrxowZg/vvv1/lepw5cwZ33HEHTpw4ofZNeixKWo+rMhk3sGCPBRERlUGGH44ePVpiuW7JX3AnB3d33gyNOIqVaJDhleKPr5FeC+ktkUBG6lS4J67K+lwybBPoTDAUssvjoilERERUMcYNLKo1BCw2IDcNSDte2XtDREQGJMmfUp2zpK1169YwI+MOhUjtiqoNgLN7nMMhsc45wERERHq5/vrr0a1btxJvCw2wipj+YtzAQsiUUwkszuwCGl1Z2XtDREQGI2t2yEZmGAoRXDOEiMgvAn4NKfKIHtNqDd5jwZkhRES+JN39skDWqVOnUL16dddiWXoc4KTSpayr4XFJb4MoqIS2S2Aozyn/R3lOmYFSXiYJLNhjQUTkC7LCZt26dXH48GHdqlNqBzopCCVLi+sVrAQLRyW2XepoyAquFQlojB1YJBQOhaQcAnIzgTDPF6ghIiLPyAwIqVZpt9t1e0x5rEWLFqlFucyWBGmvpLZLkBgSElLhYMbYgUV0AhBZDcg6C5zZDdRqV9l7RERkSHJQkk3Px5NKk1L+2myBhS3I2278gSvmWRAREfmNCQILzgwhIiLyF8MEFk///Ac+2m7FidTsojewx4KIiMhvDBNYzN9+CpvPWXE6PbfkHgspkkVEREQ+ZZjAIibCmYeamm0vpcdit0wOroQ9IyIiMg/DBBZxkc7M2dSsvKI3xNcHrKFAXhaQerhydo6IiMgkDNhjUSywsIUACY2d15lnQURE5FOGCSxiI5w9FmnFh0JEQpMLwyFERETkM8YJLCKdPRYpxYdCBGeGEBER+YXhhkJK7LFgYEFEROQXxkveLJ5jIbgYGRERkV8YJrCILW26qUgszLFIPw5kp/p5z4iIiMzDMIFFjCt5s4Qei4g4oEoN53UWyiIiIvIZw/VYpGSVsmwvh0OIiIh8zhw5FkUWI2MCJxERka8YcFZIaYEFZ4YQERH5muEKZGXm5sOeX8KaIAlajwWLZBEREfmKYQKLKuE21/XUkvIstKGQs3uA/FJ6NYiIiKhCDBNYhNisCLc5Ss+ziEsGQiKA/Fzg/AH/7yAREZEJGCawEFG2MnosrFa34RDODCEiIgqIwOLIkSO47bbbkJCQgMjISLRt2xZr1qxBIChcLqTkIlnuhbJYy4KIiMgnCg/Fnjl37hx69uyJq666CjNnzkT16tWxa9cuVK1aFYEg0tVjwZkhREREAR9YvPLKK0hOTsYnn3zi+l3Dhg3L/JucnBy1aVJTnSW17Xa72vQijxUZIjkWFpxNzy7xsS1VG6kGF5zaiXwdn7syae3U87UMFmZtO9vNdpuBWdsdyG33dH8sDofDmfHogVatWmHgwIE4fPgwFi5ciDp16uDBBx/EvffeW+rfjB8/HhMmTLjo95MnT0ZUVBT09NVuK1adsuL6evnoW+fiZsVl7kefHc8gJyQGv7WdqOtzExERGVlmZiZGjBiBlJQUxMbG6hNYREREqMtx48bhpptuwurVq/Hwww/jvffew6hRozzusZBej9OnT5e5Y+WJpB74YC4WHrdidO+G+Fv/wkRNd7kZCH2tvvP+j+4Eoqoh2Em7Z8+ejf79+yM01FnLwyzM2na2m+02A7O2O5DbLsfvxMTESwYWXg2FFBQUoEuXLnjxxRfVzx07dsSWLVvKDCzCw8PVVpy8WHq/YFryZnpufsmPHRoPxNYFUg8jNGU/EFe4MJkB+OL1DBZmbTvbbS5st/mEBljbPd0Xr2aF1KpVSw2HuGvZsiUOHjyIQODMsZCFyMoogMU1Q4iIiHzGq8BCZoTs2LGjyO927tyJ+vWdwwuBMyukjAQTzgwhIiIKjMDi0UcfxYoVK9RQyO7du1UC5gcffIAxY8YgEERdqo5FkR4L1rIgIiKq1MCia9eumDp1Kr7++mu0adMGzz//PN544w2MHDkSgSBSK+md5UFgwSJZREREuvMqeVNce+21agtEFypv5l16KOTsPiAvFwgJ88/OERERmYCh1grRAouUsnosYmoBYVUARz5wbp/f9o2IiMgMDLkIWW5eAbLt+SXfyWLhzBAiIiIfMVRgEWYDrBYPEjhdq5wysCAiItKToQILCSpiIkLKXoisyJTT3X7aMyIiInMwVGAhYiJCL51nwaEQIiIinzBcYBGr9ViUWctC67HYBXi+VAoRERGZLbCIiwy9dC2Lao0AixXISQHST/pv54iIiAzOcIGFK8eirFoWoRFAfD3ndRbKIiIi0o3hAovYCA96LATXDCEiItKdAQOLEC8DC/ZYEBER6cV4gYWWY1FW8qbgzBAiIiLdGS+w8KSORZEiWeyxICIi0otxA4tsD4dCzh8E7Fl+2DMiIiLjM1xgERPpQYEsEZ0IRMQDcABn9vhn54iIiAzOvMmbajEyzgwhIiLSk+ECizhtumlZdSw0nBlCRESkK8MFFjGRF3osHJcq153YxHnJIllERES6MOxQSF6BA5m5+WXfmUMhREREujJcYBEZakOIrJ/uzcwQGQopKPDD3hERERmb4QILi8XithDZJfIsqjYArCGAPRNIO+qfHSQiIjIwwwUWXlXftIUCVRs6rzOBk4iIqMKMGVh4OuVUcGYIERGRbowZWHhaJEtwzRAiIiLdGDOw8HTpdMGZIURERLoxeI6FJ0WyuBgZERGRXgwaWHiRY5FQWCRLZoXkpPl4z4iIiIzN0EMhHuVYRFUDoqs7r5/Z7eM9IyIiMjZzTzfVcGYIERGRLgwZWHhcIEvDmSFERES6MHYdC097LBKYwElERKQHYwYWHAohIiKqFMZO3sz0NLBoeiF5s+ASK6ISERGROaebpuXkoaDAcek/iK8H2MKB/Bzg/EHf7yAREZFBGbrHwuEA0nM9SOC02oCExs7rHA4hIiIqN0MGFhGhNoSHWD0vklVkOISBBRERkV8Ci/Hjx8NisRTZWrRogaBfiExwzRAiIqIKcyYjeKF169aYM2fOhQcI8foh/Dbl9FRajhe1LDgzhIiIqKK8jgokkKhZs6bH98/JyVGbJjU1VV3a7Xa16UV7LO1Sq2VxLj3bs+eJbwjp43Cc3ok8HffL14q320zM2na2m+02A7O2O5Db7un+WBwOSXH0fCjktddeQ1xcHCIiItC9e3e89NJLqFevXpl/M2HChIt+P3nyZERFRcFX3ttmxbbzVoxonI9uSZduYkh+Fq7ZdL+6PqPtJNhDon22b0RERMEmMzMTI0aMQEpKCmJjY/UJLGbOnIn09HQ0b94cx44dUwHDkSNHsGXLFsTExHjcY5GcnIzTp0+XuWPliaRmz56N/v37IzQ0FI9+uwnTNx/H/w1qjrt61PfoMULeagtL2jHk3fkbHHW6IBgUb7eZmLXtbDfbbQZmbXcgt12O34mJiZcMLLwaChk0aJDrert27dCtWzfUr18f3377Le65554S/yY8PFxtxcmL5YsXTHvc+Ogw9XN6boHnzyN5FmnHEHJuL9CgO4KJr17PYGDWtrPd5sJ2m09ogLXd032p0HTT+Ph4NGvWDLt37w7YWhYeTzcVnBlCRERUIRUKLGRYZM+ePahVqxYCdoVTT9cLKV7am4iIiHwbWPz973/HwoULsX//fixbtgzDhg2DzWbDrbfeioBdiMzT6aaCy6cTERFViFc5FocPH1ZBxJkzZ1C9enX06tULK1asUNcNNRRydi+QbwdsgTO2RUREZLjA4ptvvkGwLUTm1VBITG0gNBqwZwDn9l/owSAiIiLzrhVSJMfCmx4Lq9VtMTIOhxAREXnLsIGFaygk24scC8HS3kREROVm3MCisMciPScPefkFnv8hAwsiIqJyM2xgEVO4VohI86bXgjNDiIiIys2wgUWozYqoMFs5alm4FcnyvNo5ERERGTmwKJrA6UWPhUretADZ54HMM77bOSIiIgMydGBxIYHTix6L0EggPtl5ncMhREREXjF2YFFYyyLFmymngmuGEBERlYuxA4vyVN8UnBlCRERULoYOLMq1EJlIaOK8ZI8FERGRVwwdWJRrITLBHgsiIqJyMXZgUVjLwuseCy2wOH8AsGf7YM+IiIiMyRQ9Fl4nb1ZJAsLjAEeBc6VTIiIi8oixA4vyJm9aLEAi8yyIiIi8ZezAwpW86WWOhftwyBnmWRAREXnK4IFFSPl6LIqsGcLAgoiIyFOmGArxOsdCsEgWERGR1wwdWJS7jkXxKadcjIyIiMgjpsixyLYXICcv37s/rtoQsNiA3HQg7bhvdpCIiMhgDB1YxISHqAkeIs3bBM6QMKBqA+d1DocQERF5xNCBhdVqQZXwiiRwMs+CiIjIG4YOLCqewMmZIURERN4wfmBRoVoWWmDBHgsiIiJPGD6wiKtQLQutSNZunfeKiIjImAwfWLjKeldkymnKISA3Q+c9IyIiMh7jBxblXYhMRFUDohKc19lrQUREdEnGDyxcC5GVI8eieKEsIiIiMndgUaHqmyJBW+WUgQURERHMHlhUaCEywVoWREREHjNR8iaHQoiIiHzN+IFFRZI33WtZnNkFFBTouGdERETGY/zAIsI5FJJW3sAivj5gDQXysp3TTomIiMi8gUVcVAWTN20hQELjC70WREREZN7Awn26qcPhKN+DcM0QIiIij5gmxyI3vwDZ9nLmSHBmCBERke8Di5dffhkWiwWPPPIIAlV0mA02q6WCtSzYY0FEROTTwGL16tV4//330a5dOwQyCXy0BM6K17JgYEFERKR7YJGeno6RI0fiww8/RNWqVRE8S6eXN7AorL6ZfhzITtFxz4iIiIzFeSrvpTFjxuCaa65Bv3798K9//avM++bk5KhNk5qaqi7tdrva9KI9VkmPGRPubObZ9OzyPactCiFVasCSfgJ5x7fDUacTAkVZ7TY6s7ad7Wa7zcCs7Q7ktnu6P14HFt988w3WrVunhkI88dJLL2HChAkX/f73339HVFQU9DZ79uyLfpebIR0zVixavgaZu8s3M6QHqqE6TmDTvO9xKOE4Ak1J7TYLs7ad7TYXttt8ZgdY2zMzM/UPLA4dOoSHH35YNTYiIsKjv3nqqacwbty4Ij0WycnJGDBgAGJjY6FnJCX71b9/f4SGOoc+NDNSNmBnykk0atEag7vVK9fjW2fOA9ZtQ/u6UWh71WAEirLabXRmbTvbzXabgVnbHcht10YcdA0s1q5di5MnT6JTpwtDAfn5+Vi0aBHeeecdNeRhs9mK/E14eLjaipMXyxcvWEmPWzXa+fwZuQXlf86kFurCdm4PbAH0j/b16xkMzNp2tttc2G7zCbS2e7ovXgUWffv2xebNm4v87q677kKLFi3wxBNPXBRUBF7yZjkXIhMskkVERKRvYBETE4M2bdoU+V10dDQSEhIu+n0g0aabpmRWIBFGm3J6Zg+Qn+cs9U1ERETmqrypy3RT9SB1gZBIoMAOnD+g384REREZSIVPuxcsWIBAF6dHYGG1AglNgBObncMh2sJkREREZLIeC7eFyCrElWfBNUOIiIjMG1hEFuZYlLekt4aLkREREZXJXD0WFRkKEZwZQkREVCZzBBZajkWWHQ5H+SpvFgkszjCwICIiMm1goSVvFjikSFZ++R9IkjdF5hkg44xOe0dERGQcpggswkOsCLNZK7Z0ugiLBuKSndfZa0FERGTOwMJiseiYwMmZIURERKYOLIpOOa1gYJHAwIKIiKg05gks9FgvpEiPxW4d9oqIiMhYzBdYsJYFERGRz5gnsNAWItMrsDi3H8jL0WHPiIiIjMM8gYUe64WImJpAWBXAkQ+c3afPzhERERmEeQILvdYLsVhYKIuIiMjsgYUuK5xqmGdBRERk7sBCq2NR4eRNwTVDiIiITB5YFA6FVDh5U7DHgoiIyOSBhV51LIoUydoFVGRRMyIiIoMxX46FHj0W1RoBFiuQkwqkn6z44xERERmE6epY6JK8GRoBxNd3XudwCBERkQkDi8Iei7TsPOTL+ukVxTwLIiIi8wYWMYU9FiJdjzwLzgwhIiIyb2ARHmJDRKhVx1oWLJJFRERk2sDCPYGTU06JiIh8w1SBhaust57VN88fAnIzK/54REREBmCuwELPKadRCUBkVQAO4Oyeij8eERGRAZgrsNCmnFZ0ITJtMTL3QllERERkrsBC14XIiuRZMLAgIiIyXWCh61BIkSmnTOAkIiIyX2Ch50JkgjNDiIiITBxYaEun61Egq0gti91AQYE+j0lERBTETBVY6LoQmajaALCGAPZMIO2oPo9JREQUxEwVWOhax0LYQp0rnQoOhxAREZkssHD1WOg0FCI4M4SIiMikgYXeyZsioYnzkj0WREREZk3e1DGwYI8FERFR+QKLSZMmoV27doiNjVVb9+7dMXPmTARb8mZmbj7s+TrN4mBgQUREVL7Aom7dunj55Zexdu1arFmzBn/6058wZMgQbN26FcGgSrizx0Kk6TbltHAoRGaF5KTp85hERERmCCyuu+46DB48GE2bNkWzZs3wwgsvoEqVKlixYgWCQYjN6goudMuzkIXIopOc19lrQUREJnfhFN5L+fn5+O6775CRkaGGREqTk5OjNk1qaqq6tNvtatOL9liXesyYiBCk5+ThbFoW6saF6fLctoTGsGacRN6JbXAktYU/edpuIzJr29luttsMzNruQG67p/tjcTgcDm8eePPmzSqQyM7OVr0VkydPVr0YpRk/fjwmTJhw0e/l76KiouBvr2y04WimBQ+0zEeLeK+aXqr2Bz9BgzPzsaPG9dhe+0ZdHpOIiCiQZGZmYsSIEUhJSVF5lroFFrm5uTh48KB64O+//x4fffQRFi5ciFatWnncY5GcnIzTp0+XuWPliaRmz56N/v37IzTUmaRZkhH/W43V+8/hrVvaYVCbmro8t3XlJNjmPI2CFtcj/4aP4U+ettuIzNp2tpvtNgOztjuQ2y7H78TExEsGFl4PhYSFhaFJE2fCYufOnbF69Wq8+eabeP/990u8f3h4uNqKkxfLFy/YpR43LtI5/JFhd+j3/DVaqgvr2d2wVtKbwFevZzAwa9vZbnNhu80n0Nru6b5UuI5FQUFBkR6JYKll4ZMiWWf2AAX5+j0uERFRkPGqx+Kpp57CoEGDUK9ePaSlpak8iQULFmDWrFkIuvVC9Aws4usBtnAgPwc4fxCo1lC/xyYiIjJqYHHy5EnccccdOHbsGOLi4lSxLAkqZBwo6FY41bP6ptXm7LU4udU55ZSBBRERmZRXgcX//vc/BDufLEQmEpsWBhY7gWYD9H1sIiKiIGGqtUJEbIQPciyKlPbmYmRERGRe5gssfDEUovVYCFbfJCIiEzNdYOHKscjyUWBxhoEFERGZl+kCC9esEL0WIdMkFAYWGaeAzLP6PjYREVGQMF9gUVjHQvcei/AqQGwd5/Uzu/V9bCIioiBhwsDC2WORk1eAbLvOxay0QllM4CQiIpMyXWBRJSwEFgt8lMCpzQxhngUREZmT6QILq9XiVn1T71oWDCyIiMjcTBdYFMmz8NmUUw6FEBGROZkzsCjssdC/SFZhYHFuH5Cv82MTEREFAVMHFrrPDImpDYRGAwV5wLn9+j42ERFREDBlYHFhITKdcyysViCRM0OIiMi8TBlY+KyWheCaIUREZGLmDCxc1Td9GVhwZggREZmPOQMLX60XIlgki4iITMycgUXh0um617Eo3mPhcOj/+ERERAHMlIFFXJQPh0ISGgOwANnngYzT+j8+ERFRADNlYOGz6aYiNBKIr+e8zuEQIiIyGVPnWOheIEvDCpxERGRSJp8V4oMcC/c8Cy6fTkREJmPuAllZdjh8kWDJHgsiIjIpUxfIyitwIMuer/8TsEgWERGZlCkDi8hQG0KsFt9POT13ALBn6//4REREAcqUgYXFYvFtAmd0dSA8DoADOLtH/8cnIiIKUKYMLIoUyfJFLQuLxS3PgqW9iYjIPEwbWLgncPoE1wwhIiITMm1g4VovxBc9FoIzQ4iIyITMG1gU1rJIyWRgQUREpBfzBhaFU079UiSLi5EREZFJmDiw8HGORdWGgMUG5KYDacd88xxEREQBxryBhaust48Ci5AwoFpD53UOhxARkUmYN7Bw9Vj4aChEJHDKKRERmYvV7HUsfLbCqWAtCyIiMhnzBha+nm4quGYIERGZjGkDizi/BhbssSAiInPwKrB46aWX0LVrV8TExCApKQlDhw7Fjh07ENTJm77MsdCGQlIPAznpvnseIiKiYAwsFi5ciDFjxmDFihWYPXs27HY7BgwYgIyMDARvHQs7Cgp8VGciqhoQlXChngUREZHBOY+uHvrtt9+K/Pzpp5+qnou1a9eid+/eCMYeC6ldlZ6b5/rZJ8MhB5c7A4vaHXzzHERERMEYWBSXkpKiLqtVq1bqfXJyctSmSU1NVZfS2yGbXrTH8vQxbQDCQ6zIySvA2bQsRMovfMBWrTGsB5cj/8Q2FLTQP5/D23YbiVnbznaz3WZg1nYHcts93R+Lw1G+etMFBQW4/vrrcf78eSxZsqTU+40fPx4TJky46PeTJ09GVFQUKtPTa2xItVvweLs81In2zXM0PjETbY5+jSPxl2FNw7G+eRIiIiIfy8zMxIgRI1SnQmxsrP6BxQMPPICZM2eqoKJu3bpe9VgkJyfj9OnTZe5YeSIpyfvo378/QkM9G9YY+OZS7D2dgS/v7oJuDUvvdakIy65ZCPl2JBxJrZF370LdH7887TYKs7ad7Wa7zcCs7Q7ktsvxOzEx8ZKBRbmGQsaOHYvp06dj0aJFZQYVIjw8XG3FyYvlixfMm8eNi3LeL8Pu8N0/r0ZLdWE5uwehNhtg9c0MX1+9nsHArG1nu82F7TafQGu7p/vi1VFOOjckqJg6dSrmzZuHhg0L18IIUhemnPpwHCu+PmALA/KygZRDvnseIiKiAOBVYCFTTb/88kuVHyG1LI4fP662rKwsBHeRLB/WsrCFANUaO6+zUBYRERmcV4HFpEmT1NhKnz59UKtWLdc2ZcoUBCNXLQtf9liIxCbOS5b2JiIig/Mqx6KceZ4BPxTi04XIBNcMISIikzDtWiF+W4jMPbBg9U0iIjI4UwcWrhwLX64XUmT5dPZYEBGRsZk6sHDNCvF1j0VCYWCRfgLIOu/b5yIiIqpE5g4s/JW8GRELVKnpvM7hECIiMjBzBxb+qGNx0XAIp5wSEZFxmTuw8EcdCw1nhhARkQmYOrDQkjfTc/KQl1/g2ydjYEFERCZg6sAiJuJCGQ8JLvxTJItDIUREZFymDixCbVZEhdn8WyTr7F4g3w9DL0RERJXA1IFF0QROHx/sY+sCIZFAgR04f8C3z0VERFRJTB9YXFiIzMc9FrJcOtcMISIigzN9YOG3WhaCCZxERGRwDCz8tRCZewVOBhZERGRQDCz8NRRSpEgWq28SEZExMbAonHLq8+RNwaEQIiIyONMHFn5L3hQJhcmbWWeBjDO+fz4iIiI/M31g4RoK8UeORVgUEJfsvM5eCyIiMiAGFv5M3nTPszjDCpxERGQ8DCy06ab+WIhMMM8iIBxLycK+0xmVvRtERIbDwMKfQyGCy6dXupy8fAybuAzXvLUYJ9OyK3t3iIgMhYFFhB+TNwVrWVS6udtO4nhqNjJz87F09+nK3h0iIkMxfWChzQrxX45F4VDIuf1AXo5/npOK+GHtYdf1Zbs5O4eIiioocPjvZNOATB9YaD0W2fYC1UXuczE1gbAYwFEAnN3n++ejImToY8HOU66fl+05A4fDUan7RESB5b1Fe9B+wu+Yu+1EZe9KUDJ9YFGlsECWSPNHAqfF4pZnweEQf/tp/VHkFzjQslYsQm0WHDmfhUNnsyp7t4gogPy47gjkfOPTZfsre1eCkukDC5vVghhX9U0/D4cwsPAr6Zn4vnAY5LbL66FDcry6vmwP8yyIyOl4SjZ2n0xX1yUHiwne3jN9YFE0gdNfU0615dM5M8Sfth5NxY4TaQgLseLadrXRvXGiaziEiKj4iUaBA5i+8Vil7k8wYmDhNuXU7wmcLJLlV1pvxcDWNVXSbo/GCepn5lkQkWZJ4UyxpJhwdfnThiOVvEfBh4FFkYXI/D0Uskv65/3znCYnibnTCr8gbuhUR112rBePiFArTqfnYFdh1ycRmZecYGgzxf5xTUs1VL7xcAqL6XmJgYW/FyIT1RoBFiuQkwqkM+vYH+ZvP4nzmXbUiA3HFU2rq9+Fh9jQtUE1dX0Z61kQmd7e0xmqxo0Ml0rPZq8mzuFS9lp4h4FFkeqbfsqxCAkH4us7rzOB0y++X+v8YhjWsa46C9F0dxsOISJz0wrmdalfFRGhNgzpUFv9/NOGoxwu9QIDi8pYiExwZojfnErLwfwdJ9X1Gzs7h0E0PQoTOFfsPaOmoRKReWmBRc/CnooBrWuq4VIZCtl0OKWS9y54MLAoshCZPwMLrZbFbv89p0lJN6YEDe2T49EkKabIbW1qxyImPETNCPrjaGql7SMRVS75jlhe2HOpBRZVwkPQv1VNV68FeYaBhft0U/ZYGHo2yI2d6150W4jNim6NCvMsWM+CyLS2HElRJxhS16htnTjX74e0dw6H/LLJWVyPLo2BhVvy5sk0P67d4T4zhHxm69EUbD+ehjCbFde3c35BFMd6FkS0tPDEonujhCJ5WL2bVUd8VKgaUtV6NKhsDCwAdKjnrMC47sA5nM/M9e9QSMpBIDfTP89p4t6K/q1rIC7KGUAWp9WzWL3/LHLzCvy6f0QUmPkVGpkhck3bWuq6NmWdysbAAkDj6lXQomYM8goc+H2rn6Z/RiUAkVWd1zd/C5w/yJoWOpMgQRsXvbHTxcMgmuY1YlAtOkwto77x8HkY2bZjqfh4yT7k5TOAItJk2/Oxev+5EgMLMaSDM+n7ty3H1X1J58Bi0aJFuO6661C7dm1YLBZMmzYNRnBtO2dEOn2zn8q3ymJk1Vs6r//yMPBGW+ClZOCj/sDPDwEr3wf2LQYy2PVWXjIT5GxGLqrHSO2Ki78sNFar5cK0UwMvoy7T5R76ej2em/4HPlt+oLJ3hyhgrD1wTp2ISJ2bxtWjL7pdpp/WiY9Eek4e5m13zjAjHQOLjIwMtG/fHhMnToSRDC7s6pJCSecy/DQcMvAFoO1NQI02gDUUyE0DDq8C1n0GzHwc+Oxa4LVGwL+bAZ8PAX77P2D9l8CRtUAuK8Fdyg+FwyDDO9ZRSZpluVDe27gJnH8cS3VVGP1w0V4O+xAVHwZpnKhOmEs6+biuMIlz2noOh1zKhTXDPTRo0CC1eSonJ0dtmtRU55Q+u92uNr1oj1Xex0yOD1fDIZLoN3PzEdxUwgwC3SW1Ba6f5LyebwfO7oHl1DZYTm67cHl+v7M6p2x7F7j+1AELULUBLInN0SI1DAWbs2Cv1dZZ1dPq9b81KJX1Pz+Tkes6sxjSruYl3xeX1Xdmga87eA6pGdmIDLMhUJX3vT51nTPQElJd8Ps1B/zzPg+Qz3iwYrt93+4lu06py8sbVi31+a5tk4T3Fu5RPaGnUzNdSf9m+p/bPdwfi6MC5cQksps6dSqGDh1a6n3Gjx+PCRMmXPT7yZMnIyoqCoHk98MW/HrIhhZxBXigVWCczdnysxGTfQSx2YcRmyXbIcRkH0ZEXsk1F/ItIUiPqI3UiLpIjayLtMLLrNAE5/CLSSw4ZsHU/TYkRzvw93aXHhOVT8H4dTacz7XgwZb5aB5vrHwXmSU3obB9TWILsDvVisQIB/7RIR9uCfBEppOZB/zfaps6WZvQKQ/xzrXHSvTyRhuOZVrw50b56F7DWN8RnsjMzMSIESOQkpKC2NjYUu/n81Pbp556CuPGjSvSY5GcnIwBAwaUuWPliaRmz56N/v37IzS0fJFkqzMZ+PWNpdiVZkP3Pn9C1agwBCp7xinVq1FwfCuObpiD5LA0WE/vgM2egbisg2qDMxdJcYTHwFG9pdokt8ORVHg9ylnDIRiV9T9/b+JyAGm4+6qWGNytnkePtzB7M6ZuOIaC6k0wuH/hrJ0AVJ73uiSmnV+xWhX8mTymFwa8sRSns+yw1OuEwW2dBYACnR6f8WDEdvu23bP/OAnH6g1olBiNEcN6lnnfQ1X24d+zd2GfIxHPD+5quv95auGIw6X4PLAIDw9XW3HyYvniBavI4zatGY9WtWLVWPS8HWfw58s8OyBVivjaarM36I2NZ+ujzuDBsNlszumrJ7cBJ7Y6L0/+oYpwWXLSYJH8DdncVakJ1GgFJGmbBB4tgLDA6k3y5n8uFTS3FdauGNox2eP3Q8+mSSqwWLHvXEB9mPV4r/+6xTnbaVCbmkiKi8ZdPRviv3N24v3F+zGkY90Sx5UDla++OwId2+0bKwpng/RqmnjJ5xnaqa4KLFbtP4fTmXmoFRfps/0KxP+5p/tijsF4L1zTrpYKLH7dfCywA4uSWK0q70Jtzd3yYPJygTO7nUGGbCfkcqtzimv6cee2Z57bA1mcuRoSZNRo7bxMau38nS3w3zI/FOYS9G2ZhKrRnvc6aTNDNh0+r8q7axVZg50kacr72X3a3Kge9fHBoj1q+umCHadwVYukSt5LospN3NTWDSpL3apRuKxBNazafxa/bDyK+3o39sMeBp/AP0r4mRRCeW3WDlWFUaYqSn2DoBcS5uyVkM1dThpwcvuFgEMLOjJPq0RStW2ffuH+tnCgejPnLJYGVwCNrwJiS65mWVns+QWurO2SSniXRaaTNUiIwv4zmVi19yz6taoBI1iy+5RaMj6xSrgreIqPCsNtl9fH+4v24p35u9GnefWg6rUg0sPxlGzsOZWh8oyk4qYnru9QWwUW09YzsNAtsEhPT8fu3RcWztq3bx82bNiAatWqoV69IDvDL0GDxGi0rh2LrUdTMWvrcdwabL0W3giPAZK7Ojd36Sfdeja0bbskdgDHNzu3jV877yt5Go3/5Nzq96j0IRQ5+5YZIXIQlVK83urRJBH7zxxUgaVRAoufC4uESa0W91LF9/RqiE+W7Vdz+FftO4tuHn6xEhmtt0LWBimtMm9JJ5/jf97qnL59Ig1NaxRd2JDKEVisWbMGV111letnLTFz1KhR+PTTT2GU4RAJLH7ddMzYgUVpqiQ5t0Z9LvyuoAA4f8AZZBxdD+yZDxxdB5za5txWTARsYUC97hcCDVWfw1optSuGdayN0EvUriitnsXklQcNU88iMzcPv//hzK8Y0qFo71JSbISabvrVyoOYuGAPAwsy7fogJVXbLI0Mr17ZrDrmbj+pKvv+fWBzH+5hcPL6m7dPnz6qgl/xzShBhdDqwi/fewZn0v24MFkgkwChWkOgxTXAn/4J3DsXeGwPcNNnQKdRQFwykJ8L7FsIzHkWeP8K4D/NgB/uBTZ8DaQd9/kuytDV3O3Og+gN5azPcHnhwVXqmRjhfz9n20lVqrxetSh0SHauiePu/t6NVS/Gop2nsPlwSqXsI1FlkONWaeuDXMqQjs5cpZ82HlGPQ0VxrZAS1E+IRps6sWqJ3Fn+WjskGMlU1dZDgevfAh7ZDIxdCwx6DWg2CAirAmSccq6DMm008J/mwLs9gFn/AHbPBexZuu/OzxuOwJ7vUP+7FjXLN5VZhlCkUJpYsfcsgp28JuL69s4S/MXVS4hSt4lJCy8McRIZneRWnEjNQXiIFZ3rF67b5KH+LWsgKsyGQ2ezsO6gsdcXKg8GFqW4pq3zy/bXzc7xaboEOWglNgG63QeM+AZ4fB9w5wzgir8DtTs5Z5rITJTl7wBfDgderg98PhRY+hZwfIsuC7B9XzgbpKwFxzzhWjckyIdDZKXehTtPlTgM4u6BPs4EtJlbjmN3YclvIqPTeiu6NKiKiFDvKu1KZd6BrZ31X37iiqcXYWBxqeGQPRwOKfdMlAY9gb5PA/fNBx7fC9z4CdDxdiC2DpCfA+ydD8x+Gnivp3M9lB/vBzZOAdK87yXafjwVW46kItRmwfWFUyrLS5t2Jv/7YCaBgvTgtKwVW2aCWbMaMRjQqoaK7aRkMZEZeDPNtCRasC65eDIbjS5gYFEK6SKWTGEphfzbVt/nB5RXQYEDaYFVTr70YZM2w4Eh7wCPbgXGrAaufgVoOhAIjQIyTgKbvgGm3ufMzZjUC/j9n876Gh4Mm2hJm39qkVThKcKXNaympp/tPZ2BYyn6D9n4i3YmVVZvhebBq5qoS5mqe/hcps/3jagyyTC35NCJXl7mV2jk7xKiw9QstCWFQQo5MbC4xOwQLSINRFJhcvj7K/DPNSH4yxfr1AJaQUEtGd8MuHw0MPJb4In9wKjpQK9xQK0OzmGTE5uBZW8DXwwDXmngvJSfpaJosWETOVuYut45ZHVj5+QK754sLiRBZTAvoy7z81fuc+aIaKsylkUSO3s2SUBegUOtfEpkZJuPpCAtOw+xESFoU/hZ95asmCxTuMVPXPG0CAYWHgyHrNh7BqcDaDgkJy8f/561A9e/swRbj6ap3y3ceRrD312G2z5aiZWFkXjQCAkHGl4B9HsWuH8h8Nhu4Ib/AR1uA2JqA3nZzp4L6cGY1AP4Twtg6mhYtnyHcHsKFu92/n/k7EEKPelB6lkIqWcRjKZvOqrir64NqqrCX54Y08fZa/HN6kM4lRY473ciXw2DSD6Ve20Xb2mzQ2RKt0ztJicGFmVIrhaFdnULh0O2BMZwiBQzuuatJapaopxdDmiVhEfa5OHGTnUQYrWoLrlbPliBW95frj48QTkVKjoRaHsjMHQiMO4P4MGVwMCXgCb9gZBIZwnyjV8j5KcHcPWWv6Llz9fhyZCv8UjjowgtyNVlF6SehVi+JzhfQ5lfL7zJN5EvWem5yMkrwCdL9/lw74gqV3mnmRbXMTleTeWWKd2zC+vFEAMLj3stKns4RKLhCb9sxY3vLVOZ+zItctLITph4awc0jAFeGtYa8//eByO61VMJjNINPvKjlbhh0jLM33EyKA+OrmGTpBZA9weB274HnjwA3PEz0PMROGq0VXepl7sbo0N+we07H3IOm3x5A7B8onMRtnK2u0v9aup1PJqSjQNngivnYM+pdNXVK4Gm9v71hExHHVOYa/HF8gNIyQqG5B0i72Tb87HmwDldAgv5zGg5TFowTwwsLmlw4Rfzyn1nKq17WKLrgW8swidL96vj5PBOdTBnXG8MKnbQkB6WF4e1xcLHrsKo7vURFmJVc6zv+mQ1hkxcqiLqoA0w3IdNGl0J9J+AvL/Mx/ikd/BQ7hjMDuvrXKk1LwvYPQeY9X/Au5cDr7cEpj0IbPoOOLrBWa5cqoh6MJ2sY72qQTkcopXwvqJpoteJrH1bJKFZjSpIy8nDlysO+GgPiSrPmv3n1MJ8NWMj1FLpFaUFFlJkTor0ERchuyQ5WLevG4eNh1PU7JDbL6/vt+eWM8aXZmxTY96idlwEXhjeFlc1L3slytrxkZgwpI06+/xg0V5VsnnT4RTc+/kaNfXwr39qgqtb14S1AmOLgWLumao4VNATHXrfC/Rs4OylkHwM2Q4sBdKOARu+cm4aaygQU7Nwq+VcSM39Ul2vpYZDZA0NqWchPUHBQAJHWXVRWyzJW/KeeLBPEzwyZQP+t2Qf7u7ZUAVZREYr492jSYIuC+81SYpxrS8lqwjf7sdjRKBiYOHh7BAJLH7ddNRvbxrpXfjntM2qMpyQ531iUAtUCff8XyZrQfzz2laqANJHS/bh82X71TLZD361Dk2TqmDsn5rg2na1K5S8VJl2nUjHoQyL6vJXZw3yJaGt4tpjLGDPBg4uvxBkpBwu7LGwAymHnFsZxobGYHBYLM7uTIBjWhtYXEGHWyASXR2wBs6BV2p5yDTZiFAr+rdyFvDxlmS6/2f2DlVV8JvVB3FXz4a67ydRZVlWmF9R3mmmJRnaoY4KLKTS7e0MLBhYeDoc8uKM7ers9WRaNpJiInz2XFKMa/wvf7jOOhsmRuPl4W0rtEBUQpVwPHF1C9zfuxE+XrJPrWi562Q6Hv5mA96cswtPDmqBAYVV5ILJj4Vd/n2aJao2XiQ0wrm0u2yafDuQfgJIPebszZAt9Wixy2NqJdcQexqaWWXWzRFgw6aSd8JiA6rUUD0cF/d+SI9IbedtspKsH2tX9GtZw6sgtPg0utFXNsY/pm5RU09HdnMOqwWrrUdTVM+dDCk+OaglbiznOjIU/FIy7dh0JEWX/Ap3MqX7xZnbsHr/OVUHpm7Vyl3lubIxsPCAvEnaJ8dj46HzmLXlOG7v3sAnXdg/bzyKCb/8ocbppBPh3t6N8Gi/Zl6Xmy1NfFQYxg1ojnuuaKR6L/63dJ86u73/y7V45YZ2uLlLxWtA+EtefoErWWp44ZQvj9hCgbi6zq0s2akqyHj523k4dWQ/RrQKQeeqOUUDEAlQHPlAmvx8icStsJgLwUfhUIsr6IgpDEIkQLGFVKjozy+bnPsxpILVR2/oVFcFnZK8Om3DkaB6b2ifJ8mNkUqii3ddKF70+PcbVW+O9NSR+UhRLEkza5JUBTVi9TtBrBkXgcsbJqjH/2nDUVcStFkxsPDQtW1rqcBi+qZjugcWUszoH1M3q2V4hSyC9eqN7dCu7sWrUepBCkD9tW9T3NWrIV74dRu+XnUQT/zgPCMPlgOIBGGn0nMRHeLAlc30O/NwiYhVW3ybELx3aDtS8mvgo2u6FL1Pfp6zYqjWy1FS74es6pqTCuSmAadl21n6c1qsQHRS0aBDqpJaQ5ybBEVWG6wOCxqd3AXr6iNAaJjr9r2ns9AtfT9CI8LQRwKeHdpttguPof2sHiuklNtDEGG1YXSPmnhp1h68P3+XCjSCYchMAk4pZf7+wt3442gKLHAgzOLAtW1qwmZ1vm+e+mYFqqCDs+aJo8A5c0heA/VaB86wll7Ssu2q90qPfALDTDMtnE6up6Eda6vA4ucKBhZSTVny64I5z56BhYcGta2JF2Zsw6r9+g6HbDmSghEfrkBqdp6a3jj2qqYqJ8IfXc/yZfPisDbqeT9ffiAogguZKvbqbzvwcWGdhW7VHT59rbR6FlJ0TA5aMkzgIr0LMuQhW1kdBDnpJQQdx529HK4hmePO3g+p0SEb1pf6cHLoUxNtixX7awrgLW0SyHeosLtlk7d5BuB4znJxcOL+s83tulDfio4LB+6Lrjsu/r1cum4ruOh+IY4CXJNnh22L7aL7OBwFqpfCCgeuU5sEh26N2eW8eE373Y+lvbhhQGiks16KXEqwIUNq6lJ+73bddVvh9SK3uf998b+LdD6Pjw/08nq8M283/jtnJy5vlIB3RnSqcLl74yRu6n8ycnWbWnh62lbsOJGmctkkUd5TUj1YhtpnbT2O37eewPHUbLSIs6Jdj0w0SipfZdDKxMDCi+EQKR604dB5VSzrDh16LWQdins+W62CCikh/Z+b26sFofxJzmImXN9aXXcFFw7g5q7JAVnC/JEp67HzhHMFzhGX1UVH7Pfpc7auHYeYiBBV/leSs2RIzGvhVYDwpkCiHPpLUZDvXGbe1ftRGHzIOilyW0GeayvIt+PIoYOoUysJVjm4FuQhP9+OlbtPwpGfhza1ohEXbnH7G7e/lxyTYo9X9OeSa1fImb+6rZTb/cGifWEVlHybLofp/FznBuc4vM9I75Qr6HAPRNwCEhWMRMJqi0CrI0dhXbQFiKhy4TYJTiQYlf+f61LeD/nIy7fj141HkHnkHB6wFsC2vwDTXrfiunY1UD06pMh9L3qMkn5X2n2138t7p8z7SvAo1x3OKeOq3eGFbY9wtqewvdrvrdYwNDt+ENYVe52fIdd95LLwfq6grYTHK9b7JN+3e09lqGFmCbR80RN8VYvqmLX1hBo+vFRgkZWbj0W7TqlgYu62kxfVjdmeYsU1by/DuP7NcVfPBkVPagIcAwsvs+U3FA6HVDSwSM/Jw92frlGzPmSGxlf3dkNsRCgqw0XBxY+FPRcBElxI1+BHS/bi37N2Ije/AIlVwvDaje3Rq3FVzJjh28BCuv/lS0hm6ciYfbkCC0/Il6A2BbZ2xzLvmm+3Y92MGag5eDCsoc73zLw/TuDeLWtQIzYcy0b3hfr2LC85CKggIg/n0rMw+I35sOfa8dYtbdCjYXzRYEQFKu7BifblaHEePNVZeUnXPblP0ev2vDwsWLgIfa66Cqcy8tU07J83HUO23YECWFG7agRGdmuAa9vVQWR4aOHfFm6u61bk5BdgzFfrsXjPOVSJCMHnd3dD65rRzllEUgdFgjl7ZuGl289SWt51W7bbfUq/LS83E+lpaQgpyEakJRc2LSqSg29uunO7BDk8qpD05K9efbEPkc39KyW/zI6wgCPtbilXjv1QvgeQaeVuAUd0fgimhxUgJCwScd++d3Fw4h60FA92qje/5OdSmx0ya+sJ/LLhKJ4Y2OKiKf0SPMzbfgKztpzAwp2nkGWXf4qT9Cb1b1kDA9vUQFJ0KB75Yil2p0L1lEugInlw5V3XxN8YWHhBClL961fJ/D2Lk6nZajpneUiX+kNfr1fdZXKQ/PjOrpUWVBQPLuRj8FkABRdHzmfhb99uwIq9zgW1+reqoWbJyCwQu90/Z88yHOIMLE6rYapApM0G0WX6sNUKWGWWTTiqVovGtd1a48PF+/DGygz06NgOlcZuxw77dkyblYKZW4+rUvtANdXbd/+VjVRtFk/O6qRlb4+6And8vFJl8d/+2UZMue9yNK2h71ns3G0nMO7bjW5nog5E2QrwZN/6uK1zdVjztWCkWAAjl24BTn5OJvbt3IqGdWvCJiXrtftIz4rMSpKgtPAyLbcAKw+kIj23AFZbCLo1qo4acVHIKbBg8e6zOJySq4KwtsnV0LlhdVitRf9eBXFFfi68LOl3xe8rw2DqugSCJf2N/G8sQF6Os31yqdoqAV32hcCu8Pf5uZk4tHcn6tVOgjVffld4Py2IK/J3hZvqbSokQW6ObM4fpf+gjeyCLOmxb5t3/8zL7vcosLiqRRJiwkNU0rMcJ2Q2nxwrZC0R6ZlYvueMWopBI+v4DGhdAwNb10SX+lVd71/5bhvbqgCZNdvilVk7VW+prA11T6+GeLR/M0SFBfahO7D3LsDIm6BjvXisP3heJYiN6lG+XgsJTuZtP4nwECs+vKOLKsIVCCS4GF/YcxEIwYUcLP85bYsahogKs+GZa1vhlq7Jfk9C69HYOR4rXxRSsS/Qpl5m5ORhzrYTHi+R7q2/XNEIny07oPKLZBxYlpX3N5kpNW7KeizYKV9ZznV7ejerjtG9G6k1Trx9T0jRr//d2VUt2ifF427730p8d38P1Euo+GdRxstlkcD3C1eJlfWGXhjaFu/M36XOZp/5/TDmHcjBv29qj8RqJUyTLqbAbsfWzBmoP3gwbIU9VCVZsOMk/jp5vaqaWj8hCv8b1RU1kqqo2+RZripw4NXftjv3ax9wVVh1vHlrx0o/qSmr3RtnzEAdt565S/9R/sXBhj0LjrxsjP5kKbKzMvBU/wZokRhWZlBz0e+lx8IDMoPv6jY18d3aw3jlt+3qd+sPnS+SiCmVbSWQkE0Ka5X23pVf39S5Dvq3rqWWc5Cecgnw5djzwrC2uLKZPgsu+gIDCy/J2gsSWMjaIeUJLD5dug+fLnN23//3lg6ustGBIhCCCznDe3raFpXBLyS35Y1bOqCBDuV3y0O+CGTl1DMZuWoorDIOrGWR3pRse4GqeaIt964nmZZ3Q+e6avbQuwt247KGl8Gf9p5Kx12frlZrtkhypvTKjO7TBK1qe54cVxI5oH5212X48wcrVMLdiI9W4LvR3VErzrPVYEsi4/hjJ69XiwWKO3s0wFODWyA8xIb3buusquA+P/0PLNhxCle/sRj/vaU9rmha8QPEZ8v2q4OPnAzL+/P92zqjarFETenJempwSzX2L7lU83ecwtB3luKDO7qo6ZeGIL0jYdHOzc2ek2mYlXFCncw16DUA0GkKf0mGdKijAgtZTkEj32EScEgwIZ9Tb1SPCVeJt8M7nVDJoYfPZWHUx6vUScTT17ZS60YFmsA69QqitUNWHziLE6nZXv2tjK09N/0Pdf3xq5u7HivQaMGFrDcikbYEF98WlhX3NRluGPTGIhVUyBfhI/2a4vvR3SstqNBeDzkr1vZPj8p/X6w4oM5s9RwGub59bZ/15oy+spFK25ADosxk8heZjTPs3WUqqKgbH4HH2ufjPze1rXBQoZGD7xd/uQwNEqLUF7Ys3Hc6vXxrAslif4PfXKyCCukOf3dkJ/U5kqBCyP/mtsvr4+exvVSwKs9z+/9W4aWZ28r9XpBh1Wd+2oJnf3YGFVL868t7ul0UVLgb2rEOvh/dQy0RIHVshk1cqoZtjGzpbud6P10bVNOtLlBpujdOUP8H6VF7fkhrrHiqL6aN6amKznkbVLj7U4sa+P3R3qrMvnwWpV5Gv9cX4rs1hwJuDSgGFl6SdTg61YtXB9yZm495Vf1PzmTkw39Ll2Q8cGVgjtUXDy7kjMsfwUVOXj5enLFNfbHL+KR80UtA8Ui/ZgGRDa1V6VtW+AVV3jbKWeWIj1aqHpmRH65UU5crOkSgFYAqz9ognqqfEK2qC4p//foHzvlhsaUf1x1WQxTSgyVnfN/d3w21fTBqKFPHv7r3cjXUKbMG5GAvFRq9ObjLEIMs9ncu0442dWIx/aFepZ44NK8Zg5/G9MLIwvVn3l+4Fze+txwHzmR4td+p2Xbc/dkalXAt8aRU0H3txnYeDdW1rRuHn//aC5c1qKaGTv7y+RpMnL874A5Qelmy+8L6IL5ms1rUMNfnd1+mah5J8Sy9RIeH4JnrWmHqgz1Vz9P5TDse+36T+t7cf9q7948vVf43dhC6prBqnyw442kBrHs+XYPM3HyVCPivYW2ColiN7OOz17XyeXCx43gahkiX7KK96nluvSwZvz50RUANE2n1LNYfOqeWsPfWvtMZagl7WaFWSPVHyVm47u0lWHfQ2W1eHjM2H1PJYHIwa1zdt93ZUvQnzGZVibQD3likhmB8QQ5u/529UyU+2vMdavjxm/su92mXrwQVX/6lm3oOSaoe9ckqNXPLk8/2iA9X4t0Fe9TPsk6E9AZIIHapHA8ZJ3/vtk6IjQhRxfeueWuJq/fpUg6eycQN7y5TK2pGhtowaWRndUbszfeKtFXafNvl9dTn7rVZOzBm8jqVs2MkEvit2HtG9/VBKlP75Hj8PLanCiZleEdmrMkK2BIc6tUTWhEMLMphcFvnuhprDpxTXyxlkQ+p1KqQgieNq0erL4DQADgDL29w8fgPmzBl9UHdppHKCprXvbME24+nqelWksz60vB2KjIPJPWqRamDjxzoZNllb0xdfxjXvrVYLRBWNSoU/xvVRQVOMq4t041veX85Jq88WKEl0oe0r1gJb09IjZVvR3dX7+NTaTlqtdxxUzZ4dXbvSa/Oo1M24M25zopWMgvn7Vs7+rz7Wkg39Vd/6Yb4qFCVS/OXz1argmylkYP6NW8tVgGiFJuT/Xx+aBuv9lWKKs18pDe6NqiqAhlZv+dv324s8+AuScRD312q1vuR6cWSFyLj9+UhvRv/GtoWLw5rqwrlzdh8XAXAh85mwig2H0lRCeASwEldGqMILVzTR4ZHJGDKyStQwaGcrKyvwMmKHoLnCBdAJLlLpgap4ZAtx8pcu+Hhb9arqUKS/PfJnZchLiowM7A9DS7EEz9sLldwIV+caw+cVfkFUsJ88FuLVSKbzLS4qnl1/PbIFWo6aSAqmmfh2XCIHBzkIPHolI3IyM1XSXUzHr4CfVvWUL0LMu4qUyQlWPm/qZvx5A+byjyQFXf0fJY6qMlJ6rXt/ZOvI0MSEhTJ9E4Z5/1x/RH0/+9CXcboZXjl9o9WYdqGo2rFWplWLIvnFa8F4EsyTCFd2BIoSM/M6C/Xqvdn8c/1f37foXo1JKG3Va1Y/PLXXq6hIm9JwPr1vZfj4b5N1Wv6w7rDuPbtJSXmssjwkAyhyRCY9FLJkIoetQ1GdKuHyfc6e4UkyJepjdoqoMFO+7zK7K5gKEvvLekd++Key/D6ze3ViYv8/4ZPWqZLPlh5MbAoJ238VGaHlEbW4Ziz7aQ6K5DMaz2msgVDcCE9ETJe/NuWY6pL+77P16D3q/PR5tlZuGHScpVfINnx8gGQIYF/DW2jann4ctVYPYdDlnvwgZWcGjlzkIOEfJdJEqocPNxnHMjBa9JtnVQirwQHUvDplg9WqIDBE9M3O6dddmtYrUIzGbwlZ+RPDWqJ7x/ogUbVo3EyLQf3fLZGBVHFqwd6M1QkX4YSKEni46cyW+MyZw6Cv8kaPfJ+lPemJKtKtVfpThdSk2DkRyvw9jzJR3AekH98sEeFkvKE5BFJfQI5uNeKi1Cvx7B3l+KjxTI86FC5Wa/P2aWGh6RInASk397fXdfxe0ls/OWvPdX0WMkVuf3jVfhk6b5KzbvQ46mXFOYg9fRDfkVlfj8P71QXc8ZdqRZlbFM7TuXPVJbA6m8OssBCZnhowyHFP+BfLN/vWs/iPze1R+f6gZMvUNHgQsiUWQkucvMdaFkzBtuOO+vjbz+WqnIm5Ay9JNJ1K0lHLWrGomWtGHRrmKDrl6MvaT0W0rUqB1Ap4VucfAlLMp0ElXIAqBkbgTf/3KHUZe/lNX2wTxP1RfDXr9ersXYJSCaO7HTJssPTNzkDi+v9MAxSkk71qmLGQ1fg9dk78eHivSqIWrL7FF4e3k4VCvKU1Ma474s1KhFNzt4/uaur30vbFye9Sx/c3gV/+WyNGh6ICN2EYR3rqGGa07L4XZgNLw5vW+FVZIuT/7m8pjIdVIoqSc2bxTtPIeWMFRvO7nMNDz02oLlPenIkQJWA5akfN2Pq+iNqtWXJT2hVKw7R4TYVDMswpVxK1dLoMOd1uU1+L+P9peV5yGcjNSsPZzNzcTYjB2fS5TLX+XPhdekBOpeZW3hbDgoKbNgSshMP9GmiiuJ5S3oA1xYOC/hifZBAk1AlHK/f0kGVC6/MpHcGFuUkB0MZF5XKfZJAd3evhkWmncn0L/H3Ac3K3UUaDMGF9D6URHppZEpdy5qxaFHLGURIMBHMiyDJl26jxGg1RU8OhsWHbaQrX3JQtKTGfi2TVOnxsqb+aWRq2i9je6kl7CVAkyzv/xvcEnf3bFDiF/XxTKhgTsbFB5VzfF2v3gvZz4Gta+Cx7zap10ZqTtzUuS7+eW2rEoMvd9PWH8Hj329SQZgkpH10Rxc1bz8QyP/k7REd8eBX6/DjuiNq01YflsDPV8my8n55//bO+HLFATz/6zYsVGfcVvW/llyIm3y8SKD8T6VbXYo3yUwtKeolmydkCMsVeISHICrchsycfBU8yOfDveqkZyz4aMl+TF51SPWW3ntFI48+TxrJh5KhLOkFks+uWUSGVe4qvQwsKthrIYHFr26BhRwUxn61zjWnvCLL5wZ6cCFfdFIJTj608mWreiJqxaJVrRg0SIgOiGmivui1kIOnjF+6BxYSaEg+zbGUbDVzQooiyRehN1n6MlT24wM98OSPm9Qcdck/2XT4vOoBKP5Fsfa087WV6nvefNH6Suf6zvwRqTj5v6X7VIEgmQb78g1t0ad5Uolnr5Kg+cYcZ5KmBEev39yh0r8Qi5OCRnKQfWTKBtUt/+euyWoatq+TSeV9I1MVuzashke+Xo+jZ9Mw6Y4u6NnUPzlI8vxScVUqDUtQIcmPkjMkm+RKZeTmIT1bruer32lrXkjgIL15ZQ2JScBRNToU1aLDVe5Z1agwJFQJUycdaosKQ7UqYYgNt2LKzIVYlhqPrUfT1Mwb6Q2UYPueKxpdMmgtMs20cWJQzMQzCgYWFTCojXM4RAriSMU9m8WCez5drYYBujdKUGcXRn0zS7v+cU0rPDawRcCVuPZ1PQvJD9HqWUgin0zxemPOThVMyli7zA4ob0KdHFilyqiM88vZogQYspqrVFLUcnTkoLz2tPN9db3OXfEVIQdb6aUY2KYmHvtuI/afycSdn6xWdVv+cW1LV+lomfnx5A/OrnYhiaAlLdgUKGS4Q3qrZBqfVs/EX6SX7+cx3fHrjJmVMmYuAaNslyKfAwk2VOChAg65nq9+J+X4teBBLj0NymS9jDZVHXhsxOVYsOss/jtnlzpxe2vebnyybD/+0qsR7urVoMyS5FoCY6+mxs2vCEQMLCo6HFK/mko4+2HtYRXZS3EnSWiT8r1mOOCaoY3utLwHKQEtWftSLEpbIG14pzp4bkgbdUZW0aBNFhuS2QZjJ69TX6YyJfetWzuqHoqNh1NwJseCyFCrGm4JNJIEOPPh3nh11nY1XDZlzSG1PLSsziiJgfd9sVb18EiGviTu3lpJSZreqMwy7vJ+CNCYy0X+l3KAVwf5OP3bP6B1TfRrWUMt5PXfOTtVsC2Xksd2X+9GanmF4p+785m5Kh/Kfb0f8g8GFjrUtJDA4t+/71Q/S1feJ3d2DcpppXRp8v+VIR852A+duFR1/coZ2fND2qj1NPQedpEKjqO/XKeSOu/8ZBX+PqA5TqQ4Z41IUBGoqxxKz8uz17VWvXqPfb9RleS+4+NVrjVXVMnr2zrpsk4GmYP0aMkK0zI8NWPLMTWMtvtkuqrdILNn7r+yMe7oXt/1mZCkUxm+appURa13Q/5jrtNNH5A3ujbaIWPrH9ze+ZJV9yi4adNOJaiQXoXpf+2le1ChkS54WdJbxva16ohfFBbTuq5dYK41U/xMf+bDV7imKUtQITM/ZKoqgwoqb4AhC9HNeqS3mnElw48yPfblmdvVtHYJMmRWhLY+iL+Hr4g9FhUmkfBVzZPUksWv3dQOXSpx7jD5hyTlyswPSd6UGhTaIlO+ImPSL6thhHg8+/MWVVArOsSBXkEyL1/OICXhUZKdpVrlHT3qB3zNEgp8Mvwi+S9S8l2Kqr01dxcOns1UU3RlaXipp+N+IkD+w8BCBxNHdFLTqeRMjIxPhkIWPX6V359XijG1qBWDf03/A41tZ4KqNLzWexFoS85T8JPZZxLsyzLiUpn0rbm7caSwyJzkplzOwMLvyvXNNHHiRDRo0AARERHo1q0bVq1aBTOT8WQGFeSvolRT7r0MvWoacxVKovKSQPuWrvUw/+998MKwNmoK/J09GpY5a4QCpMdiypQpGDduHN577z0VVLzxxhsYOHAgduzYgaSkwMtQJyIi85CZaiO71VcbBUmPxeuvv457770Xd911F1q1aqUCjKioKHz88ce+2UMiIiIyZo9Fbm4u1q5di6eeesr1O6vVin79+mH58uUl/k1OTo7aNKmpqa7iJ7LpRXssPR8zGJi13WZuO9vNdpuBWdsdyG33dH8sDi+Wrjt69Cjq1KmDZcuWoXv37q7fP/7441i4cCFWrlx50d+MHz8eEyZMuOj3kydPVj0dREREFPgyMzMxYsQIpKSkIDY2tvJmhUjvhuRkuPdYJCcnY8CAAWXuWHkiqdmzZ6N///4IDTVPso5Z223mtrPdbLcZmLXdgdx2bcThUrwKLBITE2Gz2XDiRNGV7uTnmjVLXmExPDxcbcXJi+WLF8xXjxvozNpuM7ed7TYXttt8QgOs7Z7ui1fJm2FhYejcuTPmzp3r+l1BQYH62X1ohIiIiMzJ66EQGdYYNWoUunTpgssuu0xNN83IyFCzRIiIiMjcvA4sbrnlFpw6dQrPPPMMjh8/jg4dOuC3335DjRo1fLOHREREFDTKlbw5duxYtRERERG5C67FBoiIiCigMbAgIiIi3TCwICIiIt0wsCAiIiLdMLAgIiIi3fi8pHdx2tIknpYG9aYEqtQxl8cNpEplvmbWdpu57Ww3220GZm13ILddO25faokxvwcWaWlp6lLWCyEiIqLgIsfxuLg4fVY31YOUAJdVUmNiYmCxWHR7XG1xs0OHDum6uFmgM2u7zdx2tpvtNgOztjuQ2y7hggQVtWvXhtVqDZweC9mZunXr+uzx5Z8QSP8IfzFru83cdrbbXNhu84kNwLaX1VOhYfImERER6YaBBREREenGMIFFeHg4nn32WXVpJmZtt5nbznaz3WZg1nYboe1+T94kIiIi4zJMjwURERFVPgYWREREpBsGFkRERKQbBhZERESkG8MEFhMnTkSDBg0QERGBbt26YdWqVQgWL730Erp27aqqkSYlJWHo0KHYsWNHkftkZ2djzJgxSEhIQJUqVXDDDTfgxIkTRe5z8OBBXHPNNYiKilKP89hjjyEvL6/IfRYsWIBOnTqpbOMmTZrg008/RaB4+eWXVTXWRx55xPDtPnLkCG677TbVrsjISLRt2xZr1qxx3S451c888wxq1aqlbu/Xrx927dpV5DHOnj2LkSNHqgI68fHxuOeee5Cenl7kPps2bcIVV1yhPhdSye/VV19FZcnPz8fTTz+Nhg0bqjY1btwYzz//fJF1B4zS7kWLFuG6665TFQrlPT1t2rQit/uznd999x1atGih7iPvsxkzZlRKu2X9iyeeeELtQ3R0tLrPHXfcoSoxG7ndxY0ePVrd54033gj6dpfKYQDffPONIywszPHxxx87tm7d6rj33nsd8fHxjhMnTjiCwcCBAx2ffPKJY8uWLY4NGzY4Bg8e7KhXr54jPT3ddZ/Ro0c7kpOTHXPnznWsWbPGcfnllzt69Ojhuj0vL8/Rpk0bR79+/Rzr1693zJgxw5GYmOh46qmnXPfZu3evIyoqyjFu3DjHH3/84Xj77bcdNpvN8dtvvzkq26pVqxwNGjRwtGvXzvHwww8but1nz5511K9f33HnnXc6Vq5cqfZv1qxZjt27d7vu8/LLLzvi4uIc06ZNc2zcuNFx/fXXOxo2bOjIyspy3efqq692tG/f3rFixQrH4sWLHU2aNHHceuutrttTUlIcNWrUcIwcOVK9t77++mtHZGSk4/3333dUhhdeeMGRkJDgmD59umPfvn2O7777zlGlShXHm2++abh2y/vwH//4h+PHH3+UqMkxderUIrf7q51Lly5V7/VXX31Vvff/+c9/OkJDQx2bN2/2e7vPnz+vPqdTpkxxbN++3bF8+XLHZZdd5ujcuXORxzBau93J7dK22rVrO/773/8GfbtLY4jAQt6cY8aMcf2cn5+v/nEvvfSSIxidPHlSvTkXLlzo+kDKm0O+iDXbtm1T95EPp/bGtlqtjuPHj7vuM2nSJEdsbKwjJydH/fz44487WrduXeS5brnlFhXYVKa0tDRH06ZNHbNnz3ZceeWVrsDCqO1+4oknHL169Sr19oKCAkfNmjUdr732mut38lqEh4erLxMhXxryOqxevdp1n5kzZzosFovjyJEj6ud3333XUbVqVdfroD138+bNHZXhmmuucdx9991Ffjd8+HD1RWnkdhc/0PiznTfffLN63d1169bNcf/99zt8rawDrPsJhdzvwIEDhm/34cOHHXXq1FFBgZxYuAcWRmi3u6AfCsnNzcXatWtVV6L7eiTy8/LlyxGMUlJS1GW1atXUpbRPuhHd2yhdXfXq1XO1US6l26tGjRqu+wwcOFAtZrN161bXfdwfQ7tPZb9OMtQhQxnF982o7f7555/RpUsX3HTTTWropmPHjvjwww9dt+/btw/Hjx8vss9Sn1+G+NzbLd2l8jgaub+891euXOm6T+/evREWFlak3TLMdu7cOfhbjx49MHfuXOzcuVP9vHHjRixZsgSDBg0ydLuL82c7A+29X9J3nQwLSFuN3O6CggLcfvvtapi2devWF91utHYHfWBx+vRpNXbrfmAR8rN8eIONvAElx6Bnz55o06aN+p20Q95M2oevpDbKZUmvgXZbWfeRg3BWVhYqwzfffIN169apPJPijNruvXv3YtKkSWjatClmzZqFBx54AA899BA+++yzIvtd1ntaLiUocRcSEqKCUW9eG3968skn8ec//1kFh6GhoSqgkve6jCsbud3F+bOdpd0nEF4HyZ+SnItbb73VtdCWUdv9yiuvqHbI57wkRmu331c3pUufvW/ZskWdyRmdLAn88MMPY/bs2SrRyCwkeJQzkxdffFH9LAdY+Z+/9957GDVqFIzq22+/xVdffYXJkyers7YNGzaowEIS3ozcbrqY9ETefPPNKolVgmwjW7t2Ld588011AiW9M2YQ9D0WiYmJsNlsF80UkJ9r1qyJYDJ27FhMnz4d8+fPL7K0vLRDhnzOnz9fahvlsqTXQLutrPvI2YJkplfGB+7kyZNqtoZE57ItXLgQb731lroukbYR2y0zAVq1alXkdy1btlSzW9z3u6z3tFzKa+dOZsJIZrk3r40/STew1mshw1fSNfzoo4+6equM2u7i/NnO0u5Tma+DFlQcOHBAnVS4LwtuxHYvXrxYtUmGcLXvOWn73/72NzWT0YjtDvrAQrrKO3furMZu3c8I5efu3bsjGEjULkHF1KlTMW/ePDUdz520T7qO3dso42pyINLaKJebN28u8ubUPrTaQUzu4/4Y2n0q63Xq27ev2mc5c9U2OZOXrnHtuhHbLcNcxacTS95B/fr11XX5/8sXgfs+y7CNjLW6t1sCLgnONPLekfe+jNVr95FpcPJF7t7u5s2bo2rVqvC3zMxMNWbsTk4KZJ+N3O7i/NnOQHvva0GFTK2dM2eOmm7tzojtvv3229U0UffvOemlk0BbhkIN2W6HQaabSkb1p59+qrJr77vvPjXd1H2mQCB74IEH1NSzBQsWOI4dO+baMjMzi0y7lCmo8+bNU9Muu3fvrrbi0y4HDBigpqzKVMrq1auXOO3yscceU7MrJk6cGDDTTTXus0KM2m7JhA8JCVHTL3ft2uX46quv1P59+eWXRaYjynv4p59+cmzatMkxZMiQEqcjduzYUU1ZXbJkiZpZ4z49TWYayPS022+/XWWiy+dEnqeyppuOGjVKZcVr001l6p1MDZZZO0Zrt8x0kunPssnX7Ouvv66ua7Mf/NVOmX4o77V///vf6r3/7LPP+nT6YVntzs3NVdNq69atqz6r7t917jMdjNbukhSfFRKs7S6NIQILIbUJ5AAk9Sxk+qnMBQ4W8kYsaZPaFhr5wnnwwQfVdCN5Mw0bNkx9IN3t37/fMWjQIDW3Wb6w//a3vznsdnuR+8yfP9/RoUMH9To1atSoyHMEYmBh1Hb/8ssvKiCSgLhFixaODz74oMjtMiXx6aefVl8kcp++ffs6duzYUeQ+Z86cUV88UgtCptfedddd6gvOndRIkKmt8hhyUJcDWmVJTU1V/1v5nEZERKj/g8z9dz+oGKXd8n4r6TMtwZW/2/ntt986mjVrpt77Mu36119/rZR2SzBZ2ned/J1R2+1pYBGM7S4Nl00nIiIi3QR9jgUREREFDgYWREREpBsGFkRERKQbBhZERESkGwYWREREpBsGFkRERKQbBhZERESkGwYWREREpBsGFkRERKQbBhZE5JU777wTQ4cOrezdIKIAxcCCiIiIdMPAgohK9P3336Nt27aIjIxUy1v369dPLfX82Wef4aeffoLFYlHbggUL1P0PHTqklsSOj49HtWrVMGTIEOzfv/+ino4JEyagevXqamn70aNHIzc3txJbSUR6C9H9EYko6B07dgy33norXn31VQwbNgxpaWlYvHgx7rjjDhw8eBCpqan45JNP1H0liLDb7Rg4cCC6d++u7hcSEoJ//etfuPrqq7Fp0yaEhYWp+86dOxcREREqGJGg46677lJBywsvvFDJLSYivTCwIKISA4u8vDwMHz4c9evXV7+T3gshPRg5OTmoWbOm6/5ffvklCgoK8NFHH6leDCGBh/ReSBAxYMAA9TsJMD7++GNERUWhdevWeO6551QvyPPPPw+rlR2oREbATzIRXaR9+/bo27evCiZuuukmfPjhhzh37lyp99+4cSN2796NmJgYVKlSRW3Sk5GdnY09e/YUeVwJKjTSw5Genq6GUYjIGNhjQUQXsdlsmD17NpYtW4bff/8db7/9Nv7xj39g5cqVJd5fgoPOnTvjq6++uug2yacgIvNgYEFEJZIhjZ49e6rtmWeeUUMiU6dOVcMZ+fn5Re7bqVMnTJkyBUlJSSops6yejaysLDWcIlasWKF6N5KTk33eHiLyDw6FENFFpGfixRdfxJo1a1Sy5o8//ohTp06hZcuWaNCggUrI3LFjB06fPq0SN0eOHInExEQ1E0SSN/ft26dyKx566CEcPnzY9bgyA+See+7BH3/8gRkzZuDZZ5/F2LFjmV9BZCDssSCii0ivw6JFi/DGG2+oGSDSW/Gf//wHgwYNQpcuXVTQIJcyBDJ//nz06dNH3f+JJ55QCZ8yi6ROnToqT8O9B0N+btq0KXr37q0SQGXmyfjx4yu1rUSkL4vD4XDo/JhERBeROhbnz5/HtGnTKntXiMiH2P9IREREumFgQURERLrhUAgRERHphj0WREREpBsGFkRERKQbBhZERESkGwYWREREpBsGFkRERKQbBhZERESkGwYWREREpBsGFkRERAS9/D+QXRiq+vsrNAAAAABJRU5ErkJggg=="
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 14
  },
  {
   "attachments": {},
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 测试集"
   ]
  },
  {
   "cell_type": "code",
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-26T09:18:36.529991Z",
     "start_time": "2025-02-26T09:18:36.190490Z"
    }
   },
   "source": [
    "model.eval()\n",
    "loss = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\")"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.4400\n"
     ]
    }
   ],
   "execution_count": 15
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": ""
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "pytorch",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.8"
  },
  "orig_nbformat": 4
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
